/*
 * Copyright (C) 2000 - 2024 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package org.silverpeas.core.cache.service;

import org.silverpeas.core.cache.VolatileResourceCleaner;
import org.silverpeas.core.contribution.model.Contribution;
import org.silverpeas.core.contribution.model.ContributionIdentifier;
import org.silverpeas.core.security.session.SessionInfo;
import org.silverpeas.core.thread.ManagedThreadPool;
import org.silverpeas.core.util.ServiceProvider;
import org.silverpeas.kernel.util.StringUtil;
import org.silverpeas.kernel.logging.SilverLogger;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static org.silverpeas.core.contribution.model.CoreContributionType.VOLATILE;

/**
 * <p>
 * This cache allows to reference some volatile resources (an entity that is being registered into
 * persistence, but not yet validated by the user).<br>
 * Elements registered with volatile resource are cleared just after the ending of a user session.
 * </p>
 * <p>How it is working:</p>
 * <ul>
 * <li>first set the identifier of a {@link Contribution} with a volatile identifier. A volatile
 * identifier is generated by this service ({@link #newVolatileStringIdentifier()} for example).
 * The aim of the volatile identifier is to be unique into the context of the application running
 * and not persisted into repositories or at most persisted temporarily</li>
 * <li>then register the {@link Contribution} into the service with
 * {@link #register(String, String)}. By this way and for deletion
 * mainly, the elements (attachments for example) will be managed according the user session.</li>
 * <li>
 * At user session end:
 * <ul>
 * <li>If the {@link Contribution} has been validated, and so registered into repository, the
 * {@link Contribution} has no more a volatile identifier but the one given by its manager. So
 * the volatile identifier registered into the cache is pointing no resources and clean process
 * will do nothing in the end.</li>
 * <li>If the {@link Contribution} has not been validated, nothing has been persisted into
 * repository. Resources (attachment for example) linked to the volatile identifier will be
 * deleted just after the ending of the user session.
 * </li>
 * </ul>
 * </li>
 * </ul>
 * <p>
 * This above explanations are for understanding. In reality, there just need to use
 * {@link VolatileIdentifierProvider} to get a volatile identifier.
 * </p>
 * <p>
 * The system does not know services which have to clean resources linked to volatile
 * contributions (so contribution with volatile identifier indeed).<br>
 * But the system is able to call all implementations of {@link VolatileResourceCleaner} in order
 * to call the only method {@link VolatileResourceCleaner#cleanVolatileResources(String, String)}.
 * </p>
 * @author Yohann Chastagnier
 */
public class VolatileResourceCacheService {

  private final List<ContributionIdentifier> componentVolatileIds = new ArrayList<>();
  private Integer currentIntId = Integer.MAX_VALUE;
  private Long currentLongId = Long.MAX_VALUE;

  /**
   * Clears the volatile cache attached to a user session.
   * @param sessionInfo the session of a user.
   */
  public static void clearFrom(SessionInfo sessionInfo) {
    VolatileResourceCacheService cache = sessionInfo.getCache()
        .get(VolatileResourceCacheService.class.getName(), VolatileResourceCacheService.class);
    if (cache != null) {
      cache.clear();
    }
  }

  /**
   * Creates a new volatile identifier of integer type.
   * @return a new volatile identifier of integer type.
   */
  int newVolatileIntegerIdentifier() {
    String volatileId = newVolatileIntegerIdentifierAsString();
    // don't forget that when a string number starts with 0, the leading 0 is removed when
    // converting to an integer
    return Integer.parseInt(volatileId);
  }

  /**
   * Creates a new volatile identifier of integer type converted as string value.
   * @return a new volatile identifier of integer type converted as string value.
   */
  synchronized String newVolatileIntegerIdentifierAsString() {
    return "-" + --currentIntId;
  }

  /**
   * Creates a new volatile identifier of long type.
   * @return a new volatile identifier of long type.
   */
  long newVolatileLongIdentifier() {
    String volatileId = newVolatileLongIdentifierAsString();
    return Long.parseLong(volatileId);
  }

  /**
   * Creates a new volatile identifier of long type converted as string value.
   * @return a new volatile identifier of long type converted as string value.
   */
  @SuppressWarnings("WeakerAccess")
  synchronized String newVolatileLongIdentifierAsString() {
    return "-" + --currentLongId;
  }

  /**
   * Creates a new volatile identifier of string type.
   * @return a new volatile identifier of string type.
   */
  String newVolatileStringIdentifier() {
    return "volatile-" + UUID.randomUUID().toString();
  }

  /**
   * <p>
   * Registers a contribution into the volatile cache.
   * </p>
   * <p>
   * The local identifier of the given {@link Contribution} must previously be set with a
   * volatile identifier, provided by {@link #newVolatileStringIdentifier()} for example.
   * </p>
   * @param volatileId a volatile identifier.
   * @param componentInstanceId the identifier of a component instance.
   */
  void register(final String volatileId, final String componentInstanceId) {
    componentVolatileIds.add(withId(volatileId, componentInstanceId));
  }

  /**
   * Indicates if a contribution exists into the volatile cache.
   * @param contribution a contribution instance which contains the volatile identifier.
   * @return true if the contribution exists into the volatile cache, false otherwise.
   */
  public boolean contains(final Contribution contribution) {
    final ContributionIdentifier contributionId = contribution.getIdentifier();
    final String volatileId = contributionId.getLocalId();
    final String componentInstanceId = contributionId.getComponentInstanceId();
    return contains(volatileId, componentInstanceId);
  }

  /**
   * Indicates if the given identifier could be a volatile one.<br>
   * It works only with identifiers generated by {@link #newVolatileStringIdentifier()} method.
   * @param id an identifier as string.
   * @return true if the identifier is a volatile one, false otherwise.
   */
  public boolean couldBeVolatileId(final String id) {
    return id != null && id.startsWith("volatile-");
  }

  /**
   * Indicates if a contribution exists into the volatile cache with the given identifiers.
   * @param volatileId the volatile identifier.
   * @param componentInstanceId the identifier of a component instance.
   * @return true if the contribution exists into the volatile cache, false otherwise.
   */
  public boolean contains(final String volatileId, final String componentInstanceId) {
    return StringUtil.isDefined(volatileId) && StringUtil.isDefined(componentInstanceId) &&
        componentVolatileIds.contains(withId(volatileId, componentInstanceId));
  }

  /**
   * Clears all the resources referenced into this instance of volatile cache.
   */
  public void clear() {
    final VolatileResourceCacheService current = this;
    try {
      ManagedThreadPool.getPool().invoke(current::deleteAllResources);
    } catch (RuntimeException e) {
      // This treatment must not disturb the server in any case, so nothing is thrown here.
      SilverLogger.getLogger(this)
          .error("The clear of volatile cache did not end successfully...", e);
    }
  }

  /**
   * Deletes all resources linked to volatile {@link Contribution}.
   */
  private void deleteAllResources() {
    ServiceProvider.getAllServices(VolatileResourceCleaner.class).forEach(s -> {
      try {
        componentVolatileIds
            .forEach(i -> s.cleanVolatileResources(i.getLocalId(), i.getComponentInstanceId()));
      } catch (RuntimeException e) {
        // This treatment must not disturb the server in any case, so nothing is thrown here.
        SilverLogger.getLogger(this)
            .error("The clear of volatile cache did not end successfully...", e);
      }
    });
  }

  /**
   * Makes a volatile contribution identifier for internal use.
   * @param localId the identifier of a contribution.
   * @param componentInstanceId the identifier of a component instance.
   * @return the contribution identifier for the volatile cache.
   */
  private ContributionIdentifier withId(final String localId, final String componentInstanceId) {
    return ContributionIdentifier.from(componentInstanceId, localId, VOLATILE);
  }
}
